/**
 * Copyright 2011 Renato de Jesus Manzoni
 * 
 * SED - Software Engineering and Dependability Research Group
 * 
 *     http://www.sed.ic.unicamp.br/
 * 
 * Distributed Systems Laboratory (LSD) in the Institute of Computing (IC) at University of Campinas (UNICAMP)
 * 
 *     http://www.lsd.ic.unicamp.br/
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package br.unicamp.ic.lsd.wscaa.concurrent;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import br.unicamp.ic.lsd.wscaa.WSCAManager;
import br.unicamp.ic.lsd.wscaa.api.ExceptionHandler;
import br.unicamp.ic.lsd.wscaa.exception.RefreshException;
import br.unicamp.ic.lsd.wscaa.exception.WSClientException;
import br.unicamp.ic.lsd.wscaa.exception.WSDynamicException;
import br.unicamp.ic.lsd.wscaa.factories.WSExceptionFactory;

/**
 * @author Renato de Jesus Manzoni <rjmanzoni@gmail.com>
 * 
 */
public class ConcurrentExceptionSync {
    private Queue<WSDynamicException> queue = new ConcurrentLinkedQueue<WSDynamicException>();
    private long timeWindowResolution;
    private TimeUnit unit;
    private Boolean doneExceptionsHandling;
    // TODO: precisa garantir que o tempo de espera nao pode ser menor que a
    // diferenca do maior e menor dos web services invoados assincronamente ( o timeout do maior ws)
    private static final long TIME_WINDOW_RESOLUTION = 50L;
    private final Lock lock = new ReentrantLock();
    private final WSCAManager manager;
    private final Map<WSDynamicException, Boolean> doneExceptionsHandlings = new HashMap<WSDynamicException, Boolean>();
    private final ExecutorService executor = Executors.newCachedThreadPool();

    private static Log logger = LogFactory
	    .getLog(ConcurrentExceptionSync.class);

    public void init(long timeWindowResolution, TimeUnit unit) {

	this.timeWindowResolution = timeWindowResolution;

	this.unit = unit;

	// waitResolution = new ArrayList<Object>();
    }

    public void refresh(long timeoutExceptionResolution) throws RefreshException {

	if (queue == null)
	    queue = new LinkedList<WSDynamicException>();
	else {
	    if (queue.size() > 0)
		throw new RefreshException(
			"There are exceptions to be handling!!!");
	    queue.clear();
	}

	doneExceptionsHandlings.clear();
	this.timeWindowResolution = timeoutExceptionResolution;
	this.doneExceptionsHandling = true;

    }

    public ConcurrentExceptionSync(final WSCAManager manager) {
	this(TIME_WINDOW_RESOLUTION, TimeUnit.MILLISECONDS, manager);
    }

    public ConcurrentExceptionSync(long timeWindowResolution, TimeUnit unit,
	    final WSCAManager manager) {

	init(timeWindowResolution, unit);

	try {
	    refresh(timeWindowResolution);
	} catch (RefreshException e) {
	    // TODO Auto-generated catch block
	    logger.error("Exception in Refresh queue exception", e);
	    throw new RuntimeException(e);
	}

//	Thread t = new QueueMonitor();
//	t.setDaemon(true);
//	t.start();
	this.manager = manager;

    }

    public synchronized void addException(WSDynamicException e) {
	doneExceptionsHandling = false;
	doneExceptionsHandlings.put(e, false);

	synchronized (queue) {
	    queue.add(e);
	}

	executor.execute(new Thread() {
	    public void run() {
		syncException();
	    }
	});
    }

    // TODO definir um listener quando chega uma excecao?
    private void syncException() {
	List<WSDynamicException> exceptions = null;
	if (lock.tryLock()) {
	    if (logger.isTraceEnabled())
		logger.trace("Thread lock...");
	    try {
		// espera x mili segundos para os restantes das excecoes...
		synchronized (this) {
		    try {
			unit.timedWait(this, timeWindowResolution);
		    } catch (InterruptedException e) {
			logger.error("Exception in wait others thread throws",
				e);
		    }

		}

		//TODO: Se por acaso houver uma RemoteException, o  lock nao vai ser liberado!!!
		synchronized (queue) {
		    exceptions = new ArrayList<WSDynamicException>();
		    while (queue.size() > 0)
			exceptions.add(queue.remove());
		     lock.unlock();
		}
	    } finally {
		if (logger.isTraceEnabled())
		    logger.trace("Thread unlock...");
//		lock.unlock();
	    }
	}
	// TODO: deveria ser sincrono ou assincrono o metodo abaixo???
	if (exceptions != null && !exceptions.isEmpty())
	    handleExceptions(exceptions);

    }

    // TODO: acho que essa classe nao deveria ser o handler
    private synchronized void handleExceptions(
	    List<WSDynamicException> exceptions) {

	if (logger.isInfoEnabled())
	    logger.info("Handling the exceptions...");

	invokeClientHandler(exceptions);
	logger.info("Handling the exceptions OK");

	// garante que ninguem vai pegar esse objeto enquanto ele estiver sendo
	// tratado
	synchronized (doneExceptionsHandling) {

	    doneExceptionsHandling = true;

	    for (WSDynamicException key : exceptions) {
		doneExceptionsHandlings.put(key, true);
	    }
	    Collection<Boolean> values = doneExceptionsHandlings.values();
	    for (Iterator<Boolean> iterator = values.iterator(); iterator
		    .hasNext();) {
		Boolean value = (Boolean) iterator.next();
		if (value == false) {
		    doneExceptionsHandling = false;
		    break;
		}
	    }
	}
	wakeUpThreads();

    }

    private void invokeClientHandler(List<WSDynamicException> exceptions) {

	List<String> exs = new ArrayList<String>();
	for (Iterator<WSDynamicException> iterator = exceptions.iterator(); iterator
		.hasNext();) {

	    Exception exception = iterator.next();

	    if (logger.isDebugEnabled())
		logger.debug("Exceptions = " + exception);

	    exs.add(exception.toString());
	}

	String root = manager.getConfigManager()
		.findCommonLowestAncestorException(
			exs.toArray(new String[exs.size()]));
	ExceptionHandler eH = manager.getConfigManager().getHandlerInstance(
		root);

	try {
	    if (exceptions.size() == 1) {
		eH.handleException(exceptions.get(0));
	    } else {
		eH.handleException(WSExceptionFactory.createException(root));
	    }
	} catch (WSClientException e) {
	    logger.error("Exception from Client on handle exception", e);
	    //FIXME: deveria throw e
	}
    }

    // public boolean hasDoneExceptionHandling() {
    //
    // return doneExceptionsHandling;
    // }

    @SuppressWarnings("unused")
    private WSDynamicException resolutionException() {
	throw new RuntimeException("Method not implemented");
    }

    private void wakeUpThreads() {
	manager.notifyObjects();

    }

    public boolean isDoneExceptionsHandling() {

	synchronized (doneExceptionsHandling) {
	    return doneExceptionsHandling;
	}

    }

    class QueueMonitor extends Thread {

	@Override
	public void run() {
	    while (true) {
		logger.debug("Tamanho da Fila: " + queue.size());
		try {
		    Thread.sleep(1000);
		} catch (InterruptedException e) {
		    // TODO Auto-generated catch block
		    e.printStackTrace();
		}
	    }
	}
    }
}
